package service

import (
	"context"
	"encoding/json"
	"github.com/pkg/errors"
	"google.golang.org/protobuf/types/known/emptypb"
	"gorm.io/gorm"
	"inventory/api/inventory"
	"inventory/internal/conf"
	"inventory/internal/constant"
	"inventory/internal/model"
	"inventory/pkg/ecode"
	"inventory/pkg/log"
	"inventory/pkg/request_key"
	"inventory/pkg/util"
	"time"
)

// 备份库存临时表
func (s *Service) BackupStorage(ctx context.Context, _ *emptypb.Empty) (resp *inventory.DefaultResp, err error) {
	// 备份实时库存
	if err = s.dao.Execute(ctx, "DROP TABLE IF EXISTS inv_storage_bak"); err != nil {
		return
	}
	if err = s.dao.Execute(ctx, "CREATE TABLE inv_storage_bak AS (SELECT * FROM inv_storage)"); err != nil {
		return
	}
	resp = &inventory.DefaultResp{
		Status: true,
	}
	return
}

// 每月结算
func (s *Service) GenerateLastMonthStats(rawCtx context.Context, _ *emptypb.Empty) (resp *inventory.DefaultResp, err error) {
	reqID := request_key.GetRequestID(rawCtx)
	go func() {
		ctx := request_key.GenerateContextWithRequestID(reqID)
		defer ecode.AsyncFailOver(ctx, "月结数据生成", conf.Conf.Env, &err)
		now := time.Now()
		endPoint := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, util.FixedLocation)
		startPoint := endPoint.AddDate(0, -1, 0)
		monthString := startPoint.Format(util.MonthLayout)
		lastMonthString := startPoint.AddDate(0, -1, 0).Format(util.MonthLayout)
		var (
			warehouses []*model.Warehouse
		)
		pageNo := 1
		for {
			if warehouses, err = s.dao.FindAllWarehousesByPage(ctx, pageNo, 100); err != nil {
				return
			}
			if len(warehouses) == 0 {
				break
			}
			pageNo += 1
			for _, warehouse := range warehouses {
				if warehouse.StatsTime != nil && warehouse.StatsTime.After(endPoint) {
					continue
				}
				var (
					storages       []*model.StorageBak
					lastMonthStats []*model.StatsMonthly
					lastDailyStats []*model.DailyStatsView
				)
				now = time.Now()
				warehouse.StatsTime = &now
				storageKeyMap := make(map[string]uint8)
				// 1. 加载此刻库存数量作为期末库存数量
				if storages, err = s.dao.FindStoragesBackup(ctx, warehouse.ID); err != nil {
					return
				}
				storageMap := make(map[string]*model.StorageBak)
				for _, storage := range storages {
					storageMap[storage.StorageKey] = storage
				}
				if len(storageMap) == 0 {
					// 无库存记录时，直接返回
					if err = s.dao.Save(ctx, warehouse); err != nil {
						return
					}
					continue
				}
				// 2. 加载上个月期末金额
				lastMonthMap := make(map[string]*model.StatsMonthly)
				if lastMonthStats, err = s.dao.GetMonthlyStatsByMonth(
					ctx, warehouse.ID, lastMonthString); err != nil {
					return
				}
				for _, stats := range lastMonthStats {
					lastMonthMap[stats.StorageKey] = stats
					storageKeyMap[stats.StorageKey] = stats.StorageType
				}
				// 3. 加载本月日结记录
				lastDailyMap := make(map[string][]*model.DailyStatsView)
				if lastDailyStats, err = s.dao.GetReversedDailyStatsByMonth(
					ctx, warehouse.ID, startPoint, endPoint); err != nil {
					return
				}
				for _, stats := range lastDailyStats {
					if _, ok := lastDailyMap[stats.StorageKey]; !ok {
						var tmpStats []*model.DailyStatsView
						lastDailyMap[stats.StorageKey] = tmpStats
						storageKeyMap[stats.StorageKey] = stats.StorageType
					}
					lastDailyMap[stats.StorageKey] = append(lastDailyMap[stats.StorageKey], stats)
				}
				// 4. 计算月结数据
				for storageKey, storageType := range storageKeyMap {
					if storage, ok := storageMap[storageKey]; ok {
						// 库存必定存在变更记录，无变更记录(比如本月配置bom消除商品记录等)无需参与月结
						var (
							startQuantity, purchaseQuantity, scheduleQuantity, costQuantity, endQuantity, leftQuantity float64
							price, startAmount, purchaseAmount, scheduleAmount, costAmount, endAmount                  int64
							remark                                                                                     model.StorageRealTimeRemark
						)
						// 期初数量：即：上个月月末的库存数量(首次取0)
						if lastMonthRecord, hasRecord := lastMonthMap[storageKey]; hasRecord {
							startQuantity = lastMonthRecord.EndQuantity
							startAmount = lastMonthRecord.EndAmount
						}
						if err = json.Unmarshal(storage.Remark, &remark); err != nil {
							return
						}
						endQuantity = storage.Quantity  // 期末数量~结算时刻库存数量
						leftQuantity = storage.Quantity // 倒推剩余数量分布
						if records, hasDailyRecord := lastDailyMap[storageKey]; hasDailyRecord {
							for _, record := range records {
								switch record.ActionType {
								case constant.ActionPurchaseOut, constant.ActionPurchaseIn:
									purchaseQuantity += record.Quantity
									purchaseAmount += record.Amount
								case constant.ActionScheduleOut, constant.ActionScheduleIn:
									scheduleQuantity += record.Quantity
									scheduleAmount += record.Amount
								}
								// 期末金额: 基于先进先出的原则（先入库的先消耗），通过倒推批次计算获得
								if leftQuantity > 0 {
									if leftQuantity >= record.Quantity {
										// 当前批次采购数量完全在剩余库存中
										endAmount += record.Amount
										leftQuantity -= record.Quantity
									} else {
										endAmount += int64(leftQuantity * float64(record.Amount) / record.Quantity)
										leftQuantity = 0
									}
								}
							}
						}
						if leftQuantity != 0 && startQuantity != 0 {
							// 如果仍存在剩余库存，按期初平均价格计算(如果不存在期初或期初为0，endAmount无需继续叠加)
							endAmount += int64(leftQuantity * float64(startAmount) / startQuantity)
						}
						// 使用成本：即仓库当月实际产生的物料成本=期初金额+采购金额+调拨金额-期末金额
						costAmount = startAmount + purchaseAmount + scheduleAmount - endAmount
						costQuantity = startQuantity + purchaseQuantity + scheduleQuantity - endQuantity
						if endQuantity != 0 {
							price = int64(float64(endAmount) * remark.StorageRatio / endQuantity)
						} else if costQuantity != 0 {
							price = int64(float64(costAmount) * remark.StorageRatio / costQuantity)
						} else if startQuantity != 0 {
							price = int64(float64(startAmount) * remark.StorageRatio / startQuantity)
						}
						monthData := &model.StatsMonthly{
							Month:            monthString,
							WarehouseID:      warehouse.ID,
							StorageKey:       storageKey,
							StorageType:      storageType,
							StartQuantity:    startQuantity,
							PurchaseQuantity: purchaseQuantity,
							ScheduleQuantity: scheduleQuantity,
							CostQuantity:     costQuantity,
							EndQuantity:      endQuantity,
							StartAmount:      startAmount,
							PurchaseAmount:   purchaseAmount,
							ScheduleAmount:   scheduleAmount,
							CostAmount:       costAmount,
							EndAmount:        endAmount,
							Price:            price,
							Remark:           storage.Remark,
						}
						if err = s.dao.Create(ctx, monthData); err != nil {
							return
						}
						if err = s.dao.Save(ctx, warehouse); err != nil {
							return
						}
					}
				}
				log.FromContext(ctx).Info("月结成功：%s", monthString)
			}
		}
	}()
	resp = &inventory.DefaultResp{
		Status: true,
	}
	return
}

// 查看进销存表表
func (s *Service) GetMonthlySalesList(ctx context.Context, req *inventory.GetMonthSalesListRequest) (
	resp *inventory.GetMonthSalesListResponse, err error) {
	empId := req.WebToken.EmpId
	if _, err = util.ParseTime(req.Month); err != nil {
		err = ecode.BadRequest("month:" + req.Month)
		return
	}
	if _, err = s.CheckUsagePermission(ctx, empId, req.PStoreId, req.Id, constant.VisitMonthlySales); err != nil {
		return
	}
	var monthStats []*model.StatsMonthly
	statsView := make([]*inventory.MonthlyStatsView, 0)
	if monthStats, err = s.dao.GetMonthlyStatsByMonthWithStorageType(ctx, req.Id, req.Month, int(req.StorageType)); err != nil {
		return
	}
	summary := &inventory.SummaryView{}
	for _, stats := range monthStats {
		remark := &model.StorageRealTimeRemark{}
		if err = json.Unmarshal(stats.Remark, &remark); err != nil {
			return
		}
		startAmount := stats.StartAmount
		purchaseAmount := stats.PurchaseAmount
		scheduleAmount := stats.ScheduleAmount
		costAmount := stats.CostAmount
		endAmount := stats.EndAmount
		statsView = append(statsView, &inventory.MonthlyStatsView{
			StartAmount:    startAmount,
			PurchaseAmount: purchaseAmount,
			ScheduleAmount: scheduleAmount,
			CostAmount:     costAmount,
			EndAmount:      endAmount,
			StorageName:    remark.Name,
			SkuName:        remark.SkuName,
			StorageKey:     stats.StorageKey,
			StorageType:    inventory.StorageType(stats.StorageType),
		})
		summary.StartAmount = summary.StartAmount + startAmount
		summary.PurchaseAmount = summary.PurchaseAmount + purchaseAmount
		summary.ScheduleAmount = summary.ScheduleAmount + scheduleAmount
		summary.CostAmount = summary.CostAmount + costAmount
		summary.EndAmount = summary.EndAmount + endAmount
	}
	resp = &inventory.GetMonthSalesListResponse{
		Stats:   statsView,
		Summary: summary,
	}
	return
}

// 查看进销存明细
func (s *Service) GetMonthlySalesInfo(ctx context.Context, req *inventory.GetMonthSalesInfoRequest) (
	resp *inventory.GetMonthSalesInfoResponse, err error) {
	var startPoint time.Time
	empId := req.WebToken.EmpId
	if startPoint, err = util.ParseTime(req.Month); err != nil {
		err = ecode.BadRequest("month:" + req.Month)
		return
	}
	endPoint := startPoint.AddDate(0, 1, 0)
	if _, err = s.CheckUsagePermission(ctx, empId, req.PStoreId, req.Id, constant.VisitMonthlySales); err != nil {
		return
	}
	var (
		monthStats *model.StatsMonthly
		dailyStats []*model.StatsDaily
	)
	records := make([]*inventory.DailyRecordsView, 0)
	remark := model.StorageRealTimeRemark{}
	if monthStats, err = s.dao.GetMonthlyStatsByMonthAndStorageKey(ctx, req.Id, req.Month, req.StorageKey); err != nil {
		if !(errors.Cause(err) == gorm.ErrRecordNotFound) {
			return
		}
		monthStats = &model.StatsMonthly{}

	} else {
		if err = json.Unmarshal(monthStats.Remark, &remark); err != nil {
			return
		}
	}
	if dailyStats, err = s.dao.GetDailyStats(ctx, req.Id, req.StorageKey, startPoint, endPoint); err != nil {
		return
	}
	var startPrice, endPrice, startAmount, endAmount int64
	startAmount = monthStats.StartAmount
	endAmount = monthStats.EndAmount
	if monthStats.StartQuantity != 0 {
		startPrice = int64(float64(startAmount) * remark.StorageRatio / monthStats.StartQuantity)
	}
	if monthStats.EndQuantity != 0 {
		endPrice = int64(float64(endAmount) * remark.StorageRatio / monthStats.EndQuantity)
	}
	records = append(records, &inventory.DailyRecordsView{
		RecordDate:    startPoint.Format("01-02"),
		Type:          "期初库存",
		Quantity:      monthStats.StartQuantity,
		Amount:        startAmount,
		Price:         startPrice,
		OperationUnit: remark.StorageUnit,
	})
	for _, stats := range dailyStats {
		var (
			recordAmount int64
			actionDesc   string
		)
		switch stats.ActionType {
		case constant.ActionLoss, constant.ActionInventory:
			continue
		case constant.ActionPurchaseIn:
			actionDesc = "采购入库"
		case constant.ActionPurchaseOut:
			actionDesc = "采购出库"
		case constant.ActionScheduleIn:
			actionDesc = "调拨入库"
		case constant.ActionScheduleOut:
			actionDesc = "调拨出库"
		default:
			err = constant.ErrSystemError
			return
		}
		recordAmount = stats.Amount
		records = append(records, &inventory.DailyRecordsView{
			RecordDate:    stats.RecordDate.Format(util.DateLayout),
			Type:          actionDesc,
			Quantity:      stats.Quantity,
			Amount:        recordAmount,
			Price:         stats.Price,
			OperationUnit: stats.OperationUnit,
		})
	}
	records = append(records, &inventory.DailyRecordsView{
		RecordDate:    endPoint.AddDate(0, 0, -1).Format("01-02"),
		Type:          "期末库存",
		Quantity:      monthStats.EndQuantity,
		Amount:        endAmount,
		Price:         endPrice,
		OperationUnit: remark.StorageUnit,
	})
	resp = &inventory.GetMonthSalesInfoResponse{
		Records: records,
	}
	return
}
